import configparser
import os
import sys
import threading
import time

from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QIcon, QTextCursor
from PyQt5.QtWidgets import QMessageBox, QPushButton, QLineEdit, QCheckBox, QFileDialog, QApplication, QListWidgetItem
import ClientUi.plc.ModbusUi as ModbusUi
import serial
#import serial.tools.list_ports
from ClientUi.plc.ModbusTool import *
# from Net.utils import *
from get_config import *


class ModbusAssistant(ModbusUi.ModbuslUi):
    def __init__(self):
        print("ModbusAssistant 1")
        # 初始化modbus对象 用于通信
        self.modbus = ModbusMaster(None)
        super(ModbusAssistant, self).__init__()

        self.cfg_dic = {}  # 用于保存配置信息的字典
        # self.cfg_command_dic = {}
        # self.cfg_single_dic = {}
        self.current_path = os.path.dirname(os.path.realpath(__file__))  # 当前目录
        self.cfg_path = ''  # 配置文件的路径
        self.cfg_dir = 'settings'  # 配置文件目录
        self.cfg_file_name = 'cfg.ini'  # 配置文件名
        self.conf_parse = configparser.ConfigParser()  # 配置文件解析ConfigParser对象

        self.current_status = None

        # 初始化modbus配置文件
        self.modbus_cfg()
        # 初始化绑定槽
        self.unit_serial()
        print("ModbusAssistant 2")


    def __del__(self):
        print('ModbusAssistant.__del__()')

    # 初始化串口 给各个信号绑定槽
    def unit_serial(self):

        # 连接切换模式信号
        self.mset_cb_connect_mode.currentIndexChanged.connect(self.changemode)

        # 连接功能码选择信号
        self.function_code_cb.currentIndexChanged.connect(self.changefuncode)

        # 连接读写数量信号
        self.quantity_le_text.textChanged.connect(self.quantitychange)

        # 多条数据设置左键双击信号
        self.mul_data_send_listwidget.itemDoubleClicked.connect(self.itemdoubleclicked)

        # 连接添加查询块按钮信号
        self.add_query_content_btn.clicked.connect(self.add_query_fun_code)

        # 连接删除查询块按钮信号
        self.del_query_content_btn.clicked.connect(self.del_query_fun_code)

        # 启动轮询按钮信号
        self.mul_start_query_btn.clicked.connect(self.startStopQuery)

        # 连接modbus 查询到数据的信号
        self.modbus.onQueryedData.connect(self.onQueryedData)

        # 连接串口接收到数据信号
        # self.serial.showSwitchSignal.connect(self.swichStatus)

        # 串口检测按钮
        self.sset_btn_detect.clicked.connect(self.port_detect)

        # 打开/关闭连接modbus slave 按钮
        self.sset_btn_open.clicked.connect(self.modbus_open_close)

        # 更改窗口颜色下拉菜单
        self.sset_cb_color.currentTextChanged.connect(self.change_color)

        # 发送命令数据 按钮
        self.command_btn_send.clicked.connect(self.single_command_send)

        # 循环发送数据——单条
        self.loop_single_send_timer = QTimer()
        self.loop_single_send_timer.timeout.connect(self.single_command_send)

        self.loop_single_send_timer2 = QTimer()
        self.loop_single_send_timer2.timeout.connect(self.readrigsterloop)
        # self.sins_cb_loop_send.stateChanged.connect(self.single_loop_send)
        self.sins_cb_loop_send.clicked.connect(self.single_loop_send)
        # 清除接收按钮
        self.recvdata_btn_clear.clicked.connect(self.clear_receive)

        # 清除发送按钮
        self.sins_btn_clear.clicked.connect(self.clear_send)

        # 保存接收窗口数据到txt文件
        self.sins_btn_save.clicked.connect(self.save_receive_to_file)

    def changemode(self, currentindex: int):
        currenttext = self.mset_cb_connect_mode.currentText()
        if currenttext == 'Modbus RTU':
            self.serial_setting_gb.setHidden(False)
            self.net_setting_gb.setHidden(True)
        else:
            self.serial_setting_gb.setHidden(True)
            self.net_setting_gb.setHidden(False)

    def changefuncode(self, currentindex:int):
        currenttext = self.function_code_cb.currentText()
        if currenttext == 'Write Single Coil':
            self.quantity_lb.setHidden(True)
            self.quantity_le_text.setHidden(True)
            self.data_value_lb.setHidden(True)
            self.data_value_le_text.setHidden(True)
            self.coil_value_lb.setHidden(False)
            self.coil_on_value_rb.setHidden(False)
            self.coil_off_value_rb.setHidden(False)
        elif currenttext == 'Write Single Register':
            self.quantity_lb.setHidden(True)
            self.quantity_le_text.setHidden(True)
            self.data_value_lb.setHidden(False)
            self.data_value_le_text.setHidden(False)
            self.coil_value_lb.setHidden(True)
            self.coil_on_value_rb.setHidden(True)
            self.coil_off_value_rb.setHidden(True)
        else:
            self.quantity_lb.setHidden(False)
            self.quantity_le_text.setHidden(False)
            self.data_value_lb.setHidden(True)
            self.data_value_le_text.setHidden(True)
            self.coil_value_lb.setHidden(True)
            self.coil_on_value_rb.setHidden(True)
            self.coil_off_value_rb.setHidden(True)

            if currenttext in ['Write Multiple Coils', 'Write Multiple Register']:
                self.quantitychange(self.quantity_le_text.text())

    def quantitychange(self, quantity: str):
        currenttext = self.function_code_cb.currentText()
        if quantity == None or len(quantity) == 0 or len(quantity.strip()) == 0:
            # self.quantity_le_text.setText('1')
            # QMessageBox.critical(self, 'Quantity Error', '写入数量设置错误！')
            return None

        try:
            num = int(quantity)
            if num <= 0:
                self.quantity_le_text.setText('1')
                QMessageBox.critical(self, 'Quantity Error', '写入数量设置错误！')
                return None
            if currenttext == 'Write Multiple Coils':
                self.mul_data_send_listwidget.clear()
                for i in range(num):
                    item = QListWidgetItem('Coil {} = 0'.format(i))
                    self.mul_data_send_listwidget.addItem(item)
            elif currenttext == 'Write Multiple Register':
                self.mul_data_send_listwidget.clear()
                for i in range(num):
                    item = QListWidgetItem('D%04d = 0' % i)
                    self.mul_data_send_listwidget.addItem(item)

        except Exception as e:
            self.quantity_le_text.setText('1')
            QMessageBox.critical(self, 'Quantity Error', '写入数量设置错误！')
            return None

    def itemdoubleclicked(self, item: QListWidgetItem):
        content = item.text()
        dlg = None
        if content[0:1] == 'C':
            dlg = ModbusUi.SetCoilsValueUi()
        elif content[0:1] == 'D':
            dlg = ModbusUi.SetRegisterValueUi()
        if dlg.exec():
            value = dlg.getcurrentvalue()
            newcontent = content[0:content.index('=')+1] + ' {}'.format(value)
            item.setText(newcontent)
            #print(newcontent)

    def add_query_fun_code(self):
        try:
            fun_code = self.query_fun_code_cb.currentText()
            slaveid = int(self.query_salve_address_text.text())
            start_address = int(self.query_start_address_text.text())
            quantity = int(self.query_quantity_text.text())

            content = fun_code + ',id={},addr={},num={}'.format(slaveid,start_address,quantity)
            item = QListWidgetItem(content)
            self.query_context.addItem(item)
            query_command = None
            if fun_code == 'Read Coils':
                query_command = QueryCommand(cst.COILS,slave_id=slaveid,
                                             start_address=start_address,quantity=quantity)
            elif fun_code == 'Read Discrete Inputs':
                query_command = QueryCommand(cst.DISCRETE_INPUTS, slave_id=slaveid,
                                             start_address=start_address,quantity=quantity)
            elif fun_code == 'Read Holding Registers':
                query_command = QueryCommand(cst.HOLDING_REGISTERS, slave_id=slaveid,
                                             start_address=start_address, quantity=quantity)
            elif fun_code == 'Read Input Registers':
                query_command = QueryCommand(cst.ANALOG_INPUTS, slave_id=slaveid,
                                             start_address=start_address, quantity=quantity)

            self.modbus.addQueryCommand(query_command)
        except Exception as e:
            pass

    def opentcpModbus(self,ipaddress,ipport,mode):
        print(f'mode:{mode} ipaddress:{ipaddress} ipport:{ipport}')
        self.modbus.init(mode, ipaddress=ipaddress, ipport=ipport)

    def del_query_fun_code(self):
        items = self.query_context.selectedItems()
        if items == None or len(items) <= 0:
            return
        else:
            for item in items:
                row = self.query_context.row(item)
                realitem = self.query_context.takeItem(row)
                self.query_context.removeItemWidget(realitem)
                self.modbus.delQueryCommand(row)

    def startStopQuery(self):
        if self.query_context.count() == 0:
            QMessageBox.warning(self, 'Warning', '未添加查询数据，请先添加查询块！')
            return
        if self.modbus.is_open() == False:
            QMessageBox.warning(self, 'Warning', '未连接Modbus Slave！')
            return

        if self.mul_start_query_btn.text() == '启动轮询':
            ret = self.modbus.startStopQuery()
            if not ret:
                return
            self.mul_start_query_btn.setText('停止轮询')
            self.add_query_content_btn.setEnabled(False)
            self.del_query_content_btn.setEnabled(False)
            self.query_context.setEnabled(False)
        else:
            ret = self.modbus.startStopQuery()
            if not ret:
                return

            self.mul_start_query_btn.setText('启动轮询')
            self.add_query_content_btn.setEnabled(True)
            self.del_query_content_btn.setEnabled(True)
            self.query_context.setEnabled(True)

    def onQueryedData(self, data):
        # print(threading.currentThread().name)
        if len(data) > 0:
            for d in data:
                self.receive_log_view.append(str(d))
                # 让滚动条随着接收一起移动
                self.receive_log_view.moveCursor(QTextCursor.End)

    # 打开/关闭 连接modbus slave
    def modbus_open_close(self):
        # 按打连接modbus按钮
        if self.sset_btn_open.text() == '打开连接':
            if self.mset_cb_connect_mode.currentText() == 'Modbus RTU':
                if self.sset_cb_choose.currentText() == '无串口':
                    QMessageBox.warning(self, 'Connect Warning', '没有可打开的串口！')
                    return None
                else:
                    mode = self.mset_cb_connect_mode.currentText()
                    self.modbus.init(mode, port=self.get_port_name(),                               # 串口名
                                           baudrate=self.sset_cb_baud.currentText(),                # 波特率
                                           bytesize=self.sset_cb_data.currentText(),                # 数据位
                                           parity=self.sset_cb_parity.currentText(),                # 校验位
                                           stopbits=self.sset_cb_stop.currentText(),                # 停止位
                                           flowcontrol=self.sset_cb_flow_control.currentText()
                                     )

            else:
                try:
                    ipaddress = self.ip_address_text.text()
                    ipport = int(self.ip_port_text.text())
                    mode = self.mset_cb_connect_mode.currentText()
                    print(f'mode:{mode} ipaddress:{ipaddress} ipport:{ipport}')
                    self.modbus.init(mode, ipaddress=ipaddress, ipport=ipport)
                except Exception as e:
                    QMessageBox.critical(self, 'Connect Error', 'ip地址或者端口号错误！')
                    return None
            # 捕获 连接modbus slave的异常
            try:
                self.modbus.openCloseMaster(open=True)
            except serial.SerialException:
                QMessageBox.critical(self, 'Connect Error', '无法连接modbus slave！')
                return None

            # 判断 modbus 连接状态
            if self.modbus.is_open():
                self.sset_btn_open.setText('关闭连接')
                self.sset_btn_open.setIcon(QIcon('close_button.png'))
                self.modbus_state_gb.setTitle('modbus状态（已连接）')
                self.ssta_lb_status.setText('已连接')
                self.set_setting_enable(False)
            else:
                QMessageBox.critical(self, 'Connect Error', '无法连接modbus slave！')
                return None

        # 点击关闭连接按钮
        elif self.sset_btn_open.text() == '关闭连接':
            try:
                self.modbus.openCloseMaster(open=False)
            except Exception as e:
                QMessageBox.critical(self, 'Disconnect Error', 'Modbus不能正常关闭！')
                return None
            self.sset_btn_open.setText('打开连接')
            self.sset_btn_open.setIcon(QIcon('open_button.png'))
            self.modbus_state_gb.setTitle('modbus状态')
            self.ssta_lb_status.setText('未连接')
            self.set_setting_enable(True)

            # 更改已发送和已接收标签
            self.sent_count_num = 0
            self.ssta_lb_sent.setText(str(self.sent_count_num))
            self.receive_count_num = 0
            self.ssta_lb_receive.setText(str(self.receive_count_num))

    # def swichStatus(self, status:ConnectionStatus, msg: str):
    #     if self.current_status != status:
    #         self.current_status = status
    #         if self.current_status == ConnectionStatus.CONNECTED:
    #             self.ssta_lb_status.setText('已连接')
    #         if self.current_status == ConnectionStatus.CLOSED:
    #             self.ssta_lb_status.setText('已关闭')
    #         if self.current_status == ConnectionStatus.CONNECTING:
    #             self.ssta_lb_status.setText('连接中...')
    #         if self.current_status == ConnectionStatus.LOSE:
    #             self.ssta_lb_status.setText('异常断开')

    # 串口检测
    def port_detect(self):
        # 检测所有存在的串口 将信息存在字典中
        self.port_dict = {}
        # serial.tools.list_ports.comports()返回计算机上所有的port口信息
        # 将其存在列表中
        port_list = list(serial.tools.list_ports.comports())
        # 清除下拉列表中已有的选项
        self.sset_cb_choose.clear()
        for port in port_list:
            # 添加到字典里
            self.port_dict["%s" % port[0]] = "%s" % port[1]
            # 添加到下拉列表选项
            self.sset_cb_choose.addItem(port[0] + '：' + port[1])
        if len(self.port_dict) == 0:
            self.sset_cb_choose.addItem('无串口')
        self.sset_btn_open.setEnabled(True)

    # 获取端口号（串口选择界面想显示完全 但打开串口只需要串口号COMX）
    def get_port_name(self):
        full_name = self.sset_cb_choose.currentText()
        # rfind会找到：的位置
        com_name = full_name[0:full_name.rfind('：')]
        return com_name

    # 设置 串口设置区 可用与禁用
    def set_setting_enable(self, enable):
        self.sset_cb_choose.setEnabled(enable)
        self.sset_cb_baud.setEnabled(enable)
        self.sset_cb_data.setEnabled(enable)
        self.sset_cb_parity.setEnabled(enable)
        self.sset_cb_stop.setEnabled(enable)
        self.sset_cb_flow_control.setEnabled(enable)


    # 改变窗口颜色
    def change_color(self):
        if self.sset_cb_color.currentText() == 'whiteblack':
            self.receive_log_view.setStyleSheet("QTextEdit {color:black;background-color:white}")
        elif self.sset_cb_color.currentText() == 'blackwhite':
            self.receive_log_view.setStyleSheet("QTextEdit {color:white;background-color:black}")
        elif self.sset_cb_color.currentText() == 'blackgreen':
            self.receive_log_view.setStyleSheet("QTextEdit {color:rgb(0,255,0);background-color:black}")


    def readrigsterloop(self):
        slaveid = 1
        startaddress = 601
        quantity = 1
        #print(f'slaveid:{slaveid} startaddress:{startaddress} quantity:{quantity}')
        value = self.modbus.readHoldingRegisters(slaveid, startaddress, quantity)

    # 单条命令发送
    def single_command_send(self):
        # 获取已输入的字符串
        if self.modbus.is_open():
            fun_code = self.function_code_cb.currentText()
            try:
                slaveid = int(self.slave_id_text.text())
                startaddress = int(self.slave_address_text.text())
                quantity = int(self.quantity_le_text.text())
            except Exception as e:
                QMessageBox.warning(self, 'Connect Warning', '(从站ID)/(起始地址)/(读/写数量)设置错误！')
                return None

            if fun_code in ModbusUi.MODBUS_FUN_CODE[4:8]:
                # 写数据命令
                try:
                    value = None
                    if fun_code in ModbusUi.MODBUS_FUN_CODE[4:6]:
                        # 写单个数据值
                        if fun_code == 'Write Single Coil':
                            writevalue = self.coils_btngroup.checkedId()
                            value = self.modbus.writeSingleCoil(slaveid, startaddress, writevalue)
                        elif fun_code == 'Write Single Register':
                            writevalue = int(self.data_value_le_text.text())
                            value = self.modbus.writeSingleRegister(slaveid, startaddress, writevalue)
                    else:
                        # 写多个数据值
                        count = self.mul_data_send_listwidget.count()
                        if count <= 0:
                            return

                        data = []
                        for i in range(count):
                            item = self.mul_data_send_listwidget.item(i)
                            content = item.text()
                            value1 = int(content[content.index('=')+2:])
                            data.append(value1)

                        if fun_code == 'Write Multiple Coils':
                            value = self.modbus.writeMultipleCoil(slaveid, startaddress, data)
                        elif fun_code == 'Write Multiple Register':
                            value = self.modbus.writeMultipleRegister(slaveid, startaddress, data)

                except Exception as e:
                    print(f'e1:{e}')
            else:
                # 读取数据命令
                try:
                    value = None
                    if fun_code == 'Read Coils':
                        value = self.modbus.readCoils(slaveid, startaddress, quantity)
                    elif fun_code == 'Read Discrete Inputs':
                        value = self.modbus.readDiscreteInputs(slaveid, startaddress, quantity)
                    elif fun_code == 'Read Holding Registers':
                        print(f'slaveid:{slaveid} startaddress:{startaddress} quantity:{quantity}')
                        value = self.modbus.readHoldingRegisters(slaveid, startaddress, quantity)
                    elif fun_code == 'Read Input Registers':
                        value = self.modbus.readInputRegisters(slaveid, startaddress, quantity)

                    # 显示读取到的数据
                    if value:
                        self.receive_log_view.append(str(value))
                        # 让滚动条随着接收一起移动
                        self.receive_log_view.moveCursor(QTextCursor.End)

                except Exception as e:
                    print(f'e2:{e}')
                pass

    # 无ui循环发送命令
    def single_loop_send_noui(self,waittime):
        # modbus已经打开
        # 打开定时器 按输入的数字计时
        print("modbus.is_open")
        self.loop_single_send_timer2.start(waittime)
        #self.loop_single_send_timer.stop()


    # 循环发送命令
    def single_loop_send(self):
        # modbus已经打开
        if self.modbus.is_open():
            # 循环发送复选框被勾选
            if self.sins_cb_loop_send.isChecked():
                # 循环时间文本框中输入了内容
                if self.sins_le_loop_text.text():
                    # 打开定时器 按输入的数字计时
                    self.loop_single_send_timer.start(int(self.sins_le_loop_text.text()))
                    # 将循环时间文本框和打开串口按钮设为不可选中
                    self.sins_le_loop_text.setEnabled(False)

                    self.sset_btn_open.setEnabled(False)
                else:
                    QMessageBox.warning(self, 'Value Error', '请输入1-1000000的值！')
                    self.sins_le_loop_text.setText('1000')
                    self.sins_cb_loop_send.setChecked(False)

            else:
                # 停止定时器
                self.loop_single_send_timer.stop()
                self.sins_le_loop_text.setEnabled(True)
                self.sset_btn_open.setEnabled(True)
        else:
            QMessageBox.warning(self, 'Warning', '未连接modbus，请先打开连接！')
            self.sins_cb_loop_send.setChecked(False)



    # 保存窗口
    def save_receive_to_file(self):
        file_name = QFileDialog.getSaveFileName(self, '保存窗口为txt文件', 'SaveWindow' +
                                                time.strftime('%Y_%m_%d_%H-%M-%S', time.localtime()) + '.txt')
        if file_name[1]:
            with open(file_name[0], 'w') as file:
                my_text = self.receive_log_view.toPlainText()
                file.write(my_text)
        else:
            pass

    # 清除接收
    def clear_receive(self):
        self.receive_log_view.setText('')

    # 清除发送
    def clear_send(self):
        # self.mul_data_send_listwidget.clear()
        pass

    # 初始化串口配置 定义串口设置信息 保存和读取
    def modbus_cfg(self):

        # 读取配置信息
        self.cfg_dic = self.modbus.read_cfg(self.current_path, self.cfg_dir,self.cfg_file_name)

        # # 将读取到的配置信息显示到界面上
        # self.display_cfg()


    # 保存配置信息
    def save_cfg(self):
        # 保存每一项到配置文件
        self.modbus.save_cfg()


    # 将读取到的串口配置信息显示到界面上
    def display_cfg(self):

        self.sset_cb_baud.setCurrentText(self.serial.config['baudrate'])
        self.sset_cb_data.setCurrentText(self.serial.config['bytesize'])
        self.sset_cb_stop.setCurrentText(self.serial.config['stopbits'])
        self.sset_cb_parity.setCurrentText(self.serial.config['parity'])
        self.sset_cb_flow_control.setCurrentText(self.serial.config['flowcontrol'])



    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Message', "确定要退出吗？",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        # 判断返回值，如果点击的是Yes按钮，我们就关闭组件和应用，否则就忽略关闭事件
        if reply == QMessageBox.Yes:
            self.save_cfg()
            event.accept()

        else:
            event.ignore()


if __name__ == '__main__':
    # 每一PyQt5应用程序必须创建一个应用程序对象
    app = QApplication(sys.argv)

    # 创建一个SerialAssistant对象
    su = ModbusAssistant()
    su.show()
    # 系统exit()方法确保应用程序干净的退出
    # exec_()方法的作用是“进入程序的主循环直到exit()被调用”，
    # 如果没有这个方法，运行的时候窗口会闪退
    sys.exit(app.exec_())

